// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_COMMON_INTER_PROCESS_TIME_TICKS_CONVERTER_H_
#define CONTENT_COMMON_INTER_PROCESS_TIME_TICKS_CONVERTER_H_

#include <stdint.h>

#include "base/time/time.h"
#include "content/common/content_export.h"

namespace content {

class LocalTimeDelta;
class LocalTimeTicks;
class RemoteTimeDelta;
class RemoteTimeTicks;

// On Windows, TimeTicks are not always consistent between processes as
// indicated by |TimeTicks::IsConsistentAcrossProcesses()|. Often, the values on
// one process have a static offset relative to another. Occasionally, these
// offsets shift while running.
//
// To combat this, any TimeTicks values sent from the remote process to the
// local process must be tweaked in order to appear monotonic.
//
// In order to properly tweak ticks, we need 4 reference points:
//
// - |local_lower_bound|:  A known point, recorded on the local process, that
//                         occurs before any remote values that will be
//                         converted.
// - |remote_lower_bound|: The equivalent point on the remote process. This
//                         should be recorded immediately after
//                         |local_lower_bound|.
// - |local_upper_bound|:  A known point, recorded on the local process, that
//                         occurs after any remote values that will be
//                         converted.
// - |remote_upper_bound|: The equivalent point on the remote process. This
//                         should be recorded immediately before
//                         |local_upper_bound|.
//
// Once these bounds are determined, values within the remote process's range
// can be converted to the local process's range. The values are converted as
// follows:
//
// 1. If the remote's range exceeds the local's range, it is scaled to fit.
//    Any values converted will have the same scale factor applied.
//
// 2. The remote's range is shifted so that it is centered within the
//    local's range. Any values converted will be shifted the same amount.
class CONTENT_EXPORT InterProcessTimeTicksConverter {
public:
    InterProcessTimeTicksConverter(const LocalTimeTicks& local_lower_bound,
        const LocalTimeTicks& local_upper_bound,
        const RemoteTimeTicks& remote_lower_bound,
        const RemoteTimeTicks& remote_upper_bound);

    // Returns the value within the local's bounds that correlates to
    // |remote_ms|.
    LocalTimeTicks ToLocalTimeTicks(const RemoteTimeTicks& remote_ms) const;

    // Returns the equivalent delta after applying remote-to-local scaling to
    // |remote_delta|.
    LocalTimeDelta ToLocalTimeDelta(const RemoteTimeDelta& remote_delta) const;

    // Returns true iff the TimeTicks are converted by adding a constant, without
    // scaling. This is the case whenever the remote timespan is smaller than the
    // local timespan, which should be the majority of cases due to IPC overhead.
    bool IsSkewAdditiveForMetrics() const;

    // Returns the (remote time) - (local time) difference estimated by the
    // converter. This is the constant that is subtracted from remote TimeTicks to
    // get local TimeTicks when no scaling is applied.
    base::TimeDelta GetSkewForMetrics() const;

private:
    int64_t Convert(int64_t value) const;

    // The local time which |remote_lower_bound_| is mapped to.
    int64_t local_base_time_;

    int64_t numerator_;
    int64_t denominator_;

    int64_t remote_lower_bound_;
    int64_t remote_upper_bound_;
};

class CONTENT_EXPORT LocalTimeDelta {
public:
    int ToInt32() const { return value_; }

private:
    friend class InterProcessTimeTicksConverter;
    friend class LocalTimeTicks;

    LocalTimeDelta(int value)
        : value_(value)
    {
    }

    int value_;
};

class CONTENT_EXPORT LocalTimeTicks {
public:
    static LocalTimeTicks FromTimeTicks(const base::TimeTicks& value)
    {
        return LocalTimeTicks(value.ToInternalValue());
    }

    base::TimeTicks ToTimeTicks()
    {
        return base::TimeTicks::FromInternalValue(value_);
    }

    LocalTimeTicks operator+(const LocalTimeDelta& delta)
    {
        return LocalTimeTicks(value_ + delta.value_);
    }

private:
    friend class InterProcessTimeTicksConverter;

    LocalTimeTicks(int64_t value)
        : value_(value)
    {
    }

    int64_t value_;
};

class CONTENT_EXPORT RemoteTimeDelta {
public:
    static RemoteTimeDelta FromRawDelta(int delta)
    {
        return RemoteTimeDelta(delta);
    }

private:
    friend class InterProcessTimeTicksConverter;
    friend class RemoteTimeTicks;

    RemoteTimeDelta(int value)
        : value_(value)
    {
    }

    int value_;
};

class CONTENT_EXPORT RemoteTimeTicks {
public:
    static RemoteTimeTicks FromTimeTicks(const base::TimeTicks& ticks)
    {
        return RemoteTimeTicks(ticks.ToInternalValue());
    }

    RemoteTimeDelta operator-(const RemoteTimeTicks& rhs) const
    {
        return RemoteTimeDelta(value_ - rhs.value_);
    }

private:
    friend class InterProcessTimeTicksConverter;

    RemoteTimeTicks(int64_t value)
        : value_(value)
    {
    }

    int64_t value_;
};

} // namespace content

#endif // CONTENT_COMMON_INTER_PROCESS_TIME_TICKS_CONVERTER_H_
